"""module doc string
"""

from threading import Thread, Semaphore
from Queue import Queue, Empty
import pygtk
pygtk.require('2.0')
import time
import gtk
import random
import pango
import collections
import math

class Style:
    def __init__(self, name, value):
        self.name = name
        self.value = value
        self.private = None #for internal use only

E_CLOSE = 1
E_KEY = 2
E_MOUSE = 3

C_BLACK    = Style("C_BLACK",    gtk.gdk.Color("#000"))
C_BLUE     = Style("C_BLUE",     gtk.gdk.Color("#00A"))
C_GREEN    = Style("C_GREEN",    gtk.gdk.Color("#0A0"))
C_CYAN     = Style("C_CYAN",     gtk.gdk.Color("#0AA"))
C_RED      = Style("C_RED",      gtk.gdk.Color("#A00"))
C_MAGENTA  = Style("C_MAGENTA",  gtk.gdk.Color("#A0A"))
C_BROWN    = Style("C_BROWN",    gtk.gdk.Color("#A50"))
C_LGRAY    = Style("C_LGRAY",    gtk.gdk.Color("#AAA"))
C_DGRAY    = Style("C_DGRAY",    gtk.gdk.Color("#555"))
C_LBLUE    = Style("C_LBLUE",    gtk.gdk.Color("#55F"))
C_LGREEN   = Style("C_LGREEN",   gtk.gdk.Color("#5F5"))
C_LCYAN    = Style("C_LCYAN",    gtk.gdk.Color("#5FF"))
C_LRED     = Style("C_LRED",     gtk.gdk.Color("#F55"))
C_LMAGENTA = Style("C_LMAGENTA", gtk.gdk.Color("#F5F"))
C_YELLOW   = Style("C_YELLOW",   gtk.gdk.Color("#FF5"))
C_WHITE    = Style("C_WHITE",    gtk.gdk.Color("#FFF"))
COLORS = (C_BLACK, C_BLUE, C_GREEN, C_CYAN, 
          C_RED, C_MAGENTA, C_BROWN, C_LGRAY, 
          C_DGRAY, C_LBLUE, C_LGREEN, C_LCYAN, 
          C_LRED, C_LMAGENTA, C_YELLOW, C_WHITE)

LINE_SOLID  = Style("LINE_SOLID",  ())
LINE_DOTTED = Style("LINE_DOTTED", (1,1))
LINE_CENTER = Style("LINE_CENTER", (5, 2, 1, 2))
LINE_DASHED = Style("LINE_DASHED", (3, 3))
LINE_STYLES = (LINE_SOLID, LINE_DOTTED, LINE_CENTER, LINE_DASHED)

FILL_SOLID       = Style("FILL_SOLID",       None)

FILL_HLINE       = Style("FILL_HLINE",       (4, 2, "\x0F\x00"))
FILL_LIGHT_HLINE = Style("FILL_LIGHT_HLINE", (4, 3, "\x0F\x00\x00"))
FILL_BOLD_HLINE  = Style("FILL_BOLD_HLINE",  (4, 3, "\x0F\x0F\x00"))
FILL_WIDE_HLINE  = Style("FILL_WIDE_HLINE",  (4, 4, "\x0F\x00\x00\x00"))
FILL_VLINE       = Style("FILL_VLINE",       (2, 1, "\x05"))
FILL_LIGHT_VLINE = Style("FILL_LIGHT_VLINE", (3, 1, "\x01"))
FILL_BOLD_VLINE  = Style("FILL_BOLD_VLINE",  (3, 1, "\x03"))
FILL_WIDE_VLINE  = Style("FILL_WIDE_VLINE",  (4, 1, "\x01"))
FILL_HATCH       = Style("FILL_HATCH",       (4, 4, "\x04\xFF\x04\x04"))
FILL_X_HATCH     = Style("FILL_X_HATCH",     (6, 6, "\x01\x22\x14\x08\x14\x22"))
FILL_LIGHT_SLASH = Style("FILL_LIGHT_SLASH",    (4, 4, "\x01\x02\x04\x08"))
FILL_SLASH       = Style("FILL_SLASH",       (4, 4, "\x03\x06\x0C\x09"))
FILL_BSLASH      = Style("FILL_BSLASH",    (4, 4, "\x09\x0C\x06\x03"))
FILL_LIGHT_BSLASH= Style("FILL_LIGHT_BSLASH", (4, 4, "\x08\x04\x02\x01"))

FILL_CLOSE_INTERLEAVE  = Style("FILL_CLOSE_INTERLEAVE",  (4, 4, "\x05\x0A\x05\x0A"))
FILL_INTERLEAVE        = Style("FILL_INTERLEAVE",        (4, 4, "\x01\x04\x01\x04"))
FILL_WIDE_INTERLEAVE   = Style("FILL_WIDE_INTERLEAVE",   (8, 8, "\x01\x00\x00\x00\x10\x00\x00\x00"))
FILL_DOT         = Style("FILL_DOT",         (4, 4, "\x05\x00\x05\x00"))
FILL_WIDE_DOT    = Style("FILL_WIDE_DOT",    (4, 4, "\x01\x00\x00\x00"))
FILL_DIAMONDS    = Style("FILL_DIAMONDS",    (8, 8, "\x00\x18\x3c\x7e\x7e\x3c\x18\x00"))
FILL_WAVES       = Style("FILL_WAVES",       (8, 4, "\x06\x09\x90\x60"))
FILL_CHECKER     = Style("FILL_CHECKER",     (8, 8, "\x0F\x0F\x0F\x0F\xF0\xF0\xF0\xF0"))
FILL_BRICKS      = Style("FILL_BRICKS",      (8, 8, "\x02\x02\xFF\x20\x20\x20\xFF\x02"))

FILL_STYLES = (FILL_SOLID, FILL_CLOSE_INTERLEAVE, FILL_INTERLEAVE, FILL_WIDE_INTERLEAVE,
               FILL_HLINE, FILL_LIGHT_HLINE, FILL_BOLD_HLINE,  FILL_WIDE_HLINE,
               FILL_VLINE, FILL_LIGHT_VLINE, FILL_BOLD_VLINE,  FILL_WIDE_VLINE,
               FILL_LIGHT_SLASH, FILL_SLASH, FILL_BSLASH, FILL_LIGHT_BSLASH,
               FILL_HATCH, FILL_X_HATCH, FILL_DOT, FILL_WIDE_DOT, 
               FILL_DIAMONDS, FILL_WAVES, FILL_CHECKER, FILL_BRICKS)


A_LEFT   = 0
A_CENTER = +1
A_RIGHT  = +2
A_TOP    = 0
A_BOTTOM = +2 

DIR_DOWN   = ( 0, +1)
DIR_UP     = ( 0, -1)
DIR_RIGHT  = (+1,  0)
DIR_LEFT   = (-1,  0)

DEFAULT_BACKGROUND = C_BLACK
DEFAULT_FOREGROUND = C_WHITE
DEFAULT_FILL = True
DEFAULT_FILL_COLOR = None
DEFAULT_LINE_WIDTH = 1
DEFAULT_LINE_DASHES = LINE_SOLID
DEFAULT_T_HA = A_LEFT
DEFAULT_T_VA = A_TOP
DEFAULT_T_DIR = DIR_DOWN


__main_thread = None
__window = None
__color_stack = []
__ctx = None
__default_viewport = None
__vp = None
__width = 0
__height = 0
__pointer_x = 0
__pointer_y = 0
__solid_fill = None

def DEBUG(format, *args):
#    print format%args
    pass

def init(w, h, title = "Python Graphic"):
    global __main_thread, __window, __ctx, __width, __height, __default_viewport
    assert __window == None, "graphycs is already initialized"
    __width = w
    __height = h
    __window = _Window(__width, __height, title, lambda src: gtk.main_quit())    
    __default_viewport = _Viewport(None, 0, 0, __width, __height)
    def loop():
        __window.show()
        gtk.gdk.threads_init()
        gtk.main()
    __main_thread = Thread(target = loop)
    __main_thread.daemon = True
    __main_thread.start()  
    
    DEBUG("wait for window realization")    
    __window.wait_for_realize()
    __ctx = __window.context
    set_viewport(__default_viewport)

def destroy():
    global __main_thread, __window, __ctx, __color_stack
    assert __window != None, "graphic is not initialized"
    gtk.gdk.threads_enter()    
    __ctx = None
    __color_stack = []
    __window.destroy()
    __window = None
    gtk.gdk.threads_leave()

    DEBUG("waiting for main thread")
    __main_thread.join()
    __main_thread = None

def begin():
    """Stops automatic refreshing of the output window until end() is called. Drawing operations can be used even outside begin()...end() but each refersh slows down whole application. begin()...end() pairs can be nested.
    """ 
    __ctx.begin()
    __ctx.clip(__vp.global_x(-__vp.offset_x), __vp.global_y(-__vp.offset_y), __vp.width, __vp.height)

def end():
    __ctx.end(True)

def __sf_start(solid_fill):
    global __solid_fill
    assert __solid_fill is None
    if solid_fill and __ctx.gc.fill != gtk.gdk.SOLID:
            __solid_fill = __ctx.gc.fill        
            __ctx.gc.set_fill(gtk.gdk.SOLID)

def __sf_end():
    global __solid_fill
    if __solid_fill is not None:
        __ctx.gc.set_fill(__solid_fill)
        __solid_fill = None


    

def width():
    return __width

def height():
    return __height

def viewport():
    return __vp

def pointer_x():
    return __pointer_x

def pointer_y():
    return __pointer_y

def move_to(x, y):
    global __pointer_x, __pointer_y
    __pointer_x = x
    __pointer_y = y

def set_color(color):
    __ctx.begin()
    __ctx.set_color(color)
    __ctx.end(False)

def get_color():
    return __ctx.get_color()

def push_color(color):
    __ctx.begin()
    __color_stack.append(__ctx.get_color())
    __ctx.set_color(color)
    __ctx.end(False)
        
def pop_color():
    __ctx.begin()
    __ctx.set_color(__color_stack.pop())
    __ctx.end(False)

def set_line_style(width, dashes = DEFAULT_LINE_DASHES):
    __ctx.begin()
    if dashes.value:
        style = gtk.gdk.LINE_ON_OFF_DASH
	__ctx.gc.set_dashes(0, dashes.value)
    else:
        style = gtk.gdk.LINE_SOLID
    __ctx.gc.set_line_attributes(width, style ,gtk.gdk.CAP_BUTT, gtk.gdk.JOIN_ROUND)
    __ctx.end(False)

def set_fill_style(fill, fill_color = DEFAULT_FILL_COLOR):
    __ctx.begin()
    if fill.value:
        if fill_color:
            style = gtk.gdk.OPAQUE_STIPPLED
            __ctx.gc.set_background(__ctx.alloc_color(fill_color))
        else:
            style = gtk.gdk.STIPPLED
        __ctx.set_stipple(fill)
    else:
        style = gtk.gdk.SOLID
    __ctx.gc.set_fill(style)
    __ctx.end(False)


def offset(cx, cy):
    __default_viewport.offset(cx, cy)

def create_viewport(x, y, width, height):
    return _Viewport(__default_viewport, x, y, width, height)

def reset_viewport():
    set_viewport(__default_viewport)

def set_viewport(vp):
    DEBUG("set_viewport: "+str(vp))
    global __vp
    __vp = vp

def clear(color = DEFAULT_BACKGROUND):
    begin()
    push_color(color)
    __sf_start(True)
    __ctx.pixmap.draw_rectangle(__ctx.gc, True, __vp.global_x(-__vp.offset_x), __vp.global_y(-__vp.offset_y), __vp.width, __vp.height)
    __sf_end()
    pop_color()
    end()

def draw_point(x, y, color = None):
    DEBUG("point: %d %d", x, y)
    begin()
    if not color:
        color = __ctx.get_color()
    push_color(color)
    __sf_start(True)
    __ctx.pixmap.draw_point(__ctx.gc, __vp.global_x(x), __vp.global_y(y))
    __sf_end()
    pop_color()
    end()
    
def draw_line(ax, ay, bx, by):
    DEBUG("line: %d %d %d %d", ax, ay, bx, by)
    begin()
    __sf_start(True)
    __ctx.pixmap.draw_line(__ctx.gc, __vp.global_x(ax), __vp.global_y(ay), __vp.global_x(bx), __vp.global_y(by))
    __sf_end()
    end()

def draw_line_to(x, y):
    global __pointer_x, __pointer_y
    draw_line(__pointer_x, __pointer_y, x, y)
    __pointer_x = x
    __pointer_y = y

def draw_lines(path):
    DEBUG("line: %d", len(path))
    begin()   
    __sf_start(True)     
    __ctx.pixmap.draw_lines(__ctx.gc, __vp.global_path(path))
    __sf_end()
    end()        

def draw_polygon(path, fill = DEFAULT_FILL):
    DEBUG("line: %d", len(path))
    begin()   
    __sf_start(not fill)
    __ctx.pixmap.draw_polygon(__ctx.gc, fill, __vp.global_path(path))
    __sf_end()
    end()        

def draw_rect(x, y, w, h, fill = DEFAULT_FILL):
    DEBUG("rect: %d %d %d %d", x, y, w, h)
    begin()
    __sf_start(not fill)
    __ctx.pixmap.draw_rectangle(__ctx.gc, fill, __vp.global_x(x), __vp.global_y(y), w, h)
    __sf_end()
    end()
    
def draw_circle(x, y, radius, fill = DEFAULT_FILL):
    draw_ellipse(x, y, radius, radius, fill)

def draw_ellipse(x, y, a, b, fill = DEFAULT_FILL):
    draw_arc(x, y, a, b, 0, 360, fill)

def draw_arc(x, y, a, b, angle1, angle2, fill = DEFAULT_FILL):
    DEBUG("arc: %d %d %d %d %d %d %s", x, y, a, b, angle1, angle2, fill)
    begin()
    __sf_start(not fill)
    __ctx.pixmap.draw_arc(__ctx.gc, fill, __vp.global_x(x-a), __vp.global_y(y-b), 2*a, 2*b, int(64*angle1), int(64*angle2))
    __sf_end()
    end()


def __arc_point(x, y, a, b, phi):
    phi = phi*2*math.pi/360
    return (x + a*math.cos(phi), y - b*math.sin(phi))

def arc_info(x, y, a, b, angle1, angle2):
    return (__arc_point(x, y, a, b, angle1), __arc_point(x, y, a, b, angle2))


def draw_text(x, y, msg, ha = DEFAULT_T_HA, va = DEFAULT_T_VA, color = None):
    begin()
    __ctx.pango.set_text(msg)
    if ha != 0 or va != 0:
        w,h = __ctx.pango.get_pixel_size()
        x -= w*ha/2
        y -= h*va/2
    if not color:
        color = __ctx.get_color()
    __sf_start(True)
    __ctx.pixmap.draw_layout(__ctx.gc, __vp.global_x(x), __vp.global_y(y), __ctx.pango, __ctx.alloc_color(color))
    __sf_end()
    end()

def text_size(msg):
    __ctx.begin()
    __ctx.pango.set_text(msg)
    size = __ctx.pango.get_pixel_size()
    __ctx.end(False)
    return size

def draw_text_line(msg, ha = DEFAULT_T_HA, va = DEFAULT_T_VA, color = None, dir = DEFAULT_T_DIR):
    global __pointer_x, __pointer_y
    draw_text(__pointer_x, __pointer_y, msg, ha, va, color)
    w,h = text_size(msg)
    __pointer_x += dir[0]*w
    __pointer_y += dir[1]*h

def next_event(block = True):
    return __window.next_event(block)

def flush_events():
    return __window.flush_events()

def top_event():
    return __window.top_event()

def key_pressed(timeout = None):
    retry = True
    while retry:
        retry = False
        event = top_event()
        while event:
            if event.type == E_KEY:
                return True
            next_event() #consume the top event
            if event.type == E_CLOSE:
                raise Close()
            event = top_event();
        if timeout:
            time.sleep(timeout)
            retry = True
            timeout = None
        
    return False

def read_key():
    while True:
        event = next_event(True)
        if event:
            if event.type == E_CLOSE:
                raise Close()
            if event.type == E_KEY:
                return event

def last_key():
    global __key_event
    event = __key_event
    __key_event = None
    return event

class Close(Exception):
    pass

class Event:
    def __init__(self, etype):
        self.type = etype

class KeyEvent(Event):
    def __init__(self, string, keyval):
        Event.__init__(self, E_KEY)
        self.string = string
        self.keysym = keyval

class _Window:
    def __init__(self, width, height, title, destroy_handler):
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
        self.window.set_title(title)
        self.window.connect("delete_event", self.delete_event)
        self.window.set_border_width(0)
        self.window.set_resizable(False)
        self.window.connect("key-press-event", self.key_press)
        self.window.connect("destroy", destroy_handler)
        self.width = width
        self.height = height

        area = gtk.DrawingArea()
        area.set_size_request(width, height)
        area.connect("expose-event", self.area_expose)
        area.connect_after("realize", self.area_realize)

        self.event_queue = collections.deque(maxlen = 10)
        self.event_queue_size = Semaphore(0)
        self.gc = None
        self.realize_lock = Semaphore(0)
        self.context = None

        self.window.add(area)

    def show(self):
        self.window.show_all()

    def wait_for_realize(self):
        self.realize_lock.acquire()

    def next_event(self, block):
        if self.event_queue_size.acquire(block):
            return self.event_queue.popleft()
        return None

    def top_event(self):
        if len(self.event_queue):
            return self.event_queue[0]
        return None

    def flush_events():
        self.event_queue.clear()

    def destroy(self):
        if self.context:
            self.context.destroy()
        self.window.destroy()

    def append_event(self, event):
        self.event_queue.append(event)
        self.event_queue_size.release()

    def delete_event(self, widget, event):
        self.append_event(Event(E_CLOSE))
        return True

    def key_press(self, widget, event):
        DEBUG("key press: %d (%s)", event.keyval, event.string)
        self.append_event(KeyEvent(event.string, event.keyval))

    def area_realize(self, area):
        DEBUG("area realize")
        self.context = _Context(area, area.get_colormap(), self.width, self.height)
        self.gc = area.window.new_gc()
        self.realize_lock.release()

    def area_expose(self, area, event):
        DEBUG("area expose: %s", event.area)
        box = event.area
        area.window.draw_drawable(self.gc, self.context.pixmap, box.x, box.y, box.x, box.y, box.width, box.height)


class _Context:
    def __init__(self, area, colormap, width, height):
        self.pixmap = gtk.gdk.Pixmap(area.window, width, height)
        self.gc = self.pixmap.new_gc()
        self.pango = pango.Layout(area.get_pango_context())
        self.colormap = colormap
        self.lock_depth = 0
        self.queue_draw = area.queue_draw
        self.cliprect = gtk.gdk.Rectangle(0, 0, width, height)        
        self.color = None
        self.set_color(DEFAULT_FOREGROUND)

    def destroy(self):
        pass

    def clip(self, x, y, width, height):
        self.cliprect.x = x
        self.cliprect.y = y
        self.cliprect.width = width
        self.cliprect.height = height
        self.gc.set_clip_rectangle(self.cliprect)
        DEBUG("clip: "+str(self.cliprect))

    def begin(self):
        if not self.lock_depth:
            gtk.gdk.threads_enter()
        self.lock_depth += 1

    def end(self, repaint):
        assert self.lock_depth > 0, "graphic is not frozen"
        self.lock_depth -= 1
        if not self.lock_depth:
            gtk.gdk.threads_leave()
            if repaint:
                self.queue_draw()

    def set_color(self, color):
        self.color = color
        self.gc.set_foreground(self.alloc_color(color))

    def get_color(self):
        return self.color

    def alloc_color(self, color):
        if not color.private:
            color.private = self.colormap.alloc_color(color.value)
        return color.private

    def set_stipple(self, style):
        if not style.private:
            style.private = gtk.gdk.bitmap_create_from_data(self.pixmap, style.value[2], style.value[0], style.value[1])
        self.gc.set_stipple(style.private)

class _Viewport:
    def __init__(self, parent, x, y, width, height):
        self.parent = parent
        self.x = x
        self.y = y
        self.width = width
        self.height = height
        self.offset_x = 0
        self.offset_y = 0

    def __str__(self):
        return "%s(%d,%d,%d,%d,+%d,+%d)"%(self.__class__.__name__, self.x, self.y, self.width, self.height, self.offset_x, self.offset_y)

    def sub(self, x, y, width, height):
        return _Viewport(self, x, y, width, height)

    def border(self, top, left = None, bottom = None, right = None):
        if not left:
            left = top
        if not bottom:
            bottom = top
        if not right:
            right = left        
        return self.sub(left, top, self.width-left-right, self.height-top-bottom)

    def offset(self, cx, cy):
        self.offset_x = cx
        self.offset_y = cy

    def global_x(self, x):
        x += self.x + self.offset_x
        if self.parent:
            return self.parent.global_x(x)
        return x

    def global_y(self, y):
        y += self.y + self.offset_y
        if self.parent:
            return self.parent.global_y(y)
        return y

    def global_point(self, point):
        if self.offset_x == 0 and self.offset_y == 0:
            if self.parent:
                return self.parent.global_point(point)
            return point
        return (self.global_x(point[0]), self.global_y(point[1]))

    def global_path(self, path):
#        if not self.parent and self.offset_x == 0 and self.offset_y == 0:
#            return path
#        p = []
#        for point in path:
#            p.append(self.global_point(point))
        pp = []
        for p in path:
            pp.append((self.global_x(p[0]), self.global_y(p[1])))
        return pp

    def local_x(self, x):
        pass

    def local_y(self, y):
        pass

    def local_point(self, point):
        pass 

    def resize_to(self, width, height):
        self.width = width
        self.height = height

    def set_center(self, cx, cy):
        self.offset_x = cx
        self.offset_y = cy

    def move_to(self, x, y):
        self.x = x
        self.y = y






 


